package ds.safelock.security;

/**
 * This class provides both cipher and decipher methods
 * @author David Silva
 * @version 1.0
 */
public class Cipher {

	private Cipher(){ }

	/**
	 * Ciphers the given data
	 * @param data string that represents the data to be cipher'd
	 * @param key the key which will be used to cipher the data
	 * @param salt the salt used to harden the key
	 * @return byte array of cipher'd data in byte[0] and the initial vector generated in byte[1], null if any error occurs
	 */
	public static byte[][] cipher(String data, String key, byte[] salt)
	{ return cipher(key.getBytes(),data.toCharArray(),salt); }

	/**
	 * Ciphers the given data
	 * @param data byte array of data to be cipher'd (encoding: UTF-8)
	 * @param key the key which will be used to cipher the data
	 * @param salt the salt used to harden the key
	 * @return byte array of cipher'd data in byte[0] and the initial vector generated in byte[1], null if any error occurs
	 */
	public static byte[][] cipher(byte[] data, char[] key, byte[] salt)
	{
		try {
			byte[] keybytes = new String(key).getBytes(ds.safelock.config.Configuration.CHARSET);
			
			/* Instantiate the digest */
			final java.security.MessageDigest shaDigest = java.security.MessageDigest.getInstance(
														  ds.safelock.config.Configuration.DIGEST_ALGORITHM);

			/* mix the salt + key */
			final byte[] salted = new byte[keybytes.length + salt.length];
			System.arraycopy(keybytes, 0, salted, 0, keybytes.length);
			System.arraycopy(salt, 0, salted, keybytes.length, salt.length);
			java.util.Arrays.fill(keybytes, (byte) 0x00);

			/* digest & obtain the final key */
			shaDigest.reset();
			final byte[] finalkey = shaDigest.digest(salted);
			java.util.Arrays.fill(salted, (byte) 0x00);

			/* init key specifications */
			final java.security.spec.KeySpec keyspec = new javax.crypto.spec.PBEKeySpec(
														new String(finalkey).toCharArray(), salt, 2048, 256);
			
			/* init key factory */
			final javax.crypto.SecretKeyFactory factory = javax.crypto.SecretKeyFactory.getInstance(
															ds.safelock.config.Configuration.KEYFACTORY_ALGORITHM);
			
			/* obtain the key */
			final javax.crypto.SecretKey secretkey = new javax.crypto.spec.SecretKeySpec(
														factory.generateSecret(keyspec).getEncoded(), 
														ds.safelock.config.Configuration.KEY_ALGORITHM);
			java.util.Arrays.fill(finalkey, (byte) 0x00);

			/* init the cipher */
			final javax.crypto.Cipher c = javax.crypto.Cipher.getInstance(ds.safelock.config.Configuration.CIPHER_ALGORITHM);
			c.init(javax.crypto.Cipher.ENCRYPT_MODE, secretkey);
			final java.security.AlgorithmParameters params = c.getParameters();
			final javax.crypto.spec.IvParameterSpec paramsSec = params.getParameterSpec(javax.crypto.spec.IvParameterSpec.class);
			
			/* ciphers */
			return new byte[][]{c.doFinal(data),paramsSec.getIV()};
		}
		catch (java.security.InvalidKeyException e) { System.err.println(e.getMessage()); }
		catch (javax.crypto.IllegalBlockSizeException e) { System.err.println(e.getMessage()); }
		catch (javax.crypto.BadPaddingException e) { System.err.println(e.getMessage()); }
		catch (java.security.NoSuchAlgorithmException e) { System.err.println(e.getMessage()); } 
		catch (javax.crypto.NoSuchPaddingException e) { System.err.println(e.getMessage()); }
		catch (java.security.spec.InvalidKeySpecException e) { System.err.println(e.getMessage()); }
		catch (java.security.spec.InvalidParameterSpecException e) { System.err.println(e.getMessage()); }
		catch (java.io.UnsupportedEncodingException e) { System.err.println(e.getMessage()); }
		return null;
	}

	/**
	 * Deciphers the given cipher'd data
	 * @param data byte array of cipher'd data to be decipher'd
	 * @param key the key which will be used to decipher the data
	 * @param salt the salt used previously by cipher()
	 * @param iv the salt returned by cipher()
	 * @return byte array of decipher'd data, null if any error occurs
	 */
	public static byte[] decipher(byte[] data, char[] key, byte[] salt, byte[] iv)
	{
		try {
			byte[] keybytes = new String(key).getBytes(ds.safelock.config.Configuration.CHARSET);
			
			/* Instantiate the digest */
			final java.security.MessageDigest shaDigest = java.security.MessageDigest.getInstance(
															ds.safelock.config.Configuration.DIGEST_ALGORITHM);

			/* mix the salt + key */
			final byte[] salted = new byte[keybytes.length + salt.length];
			System.arraycopy(keybytes, 0, salted, 0, keybytes.length);
			System.arraycopy(salt, 0, salted, keybytes.length, salt.length);
			java.util.Arrays.fill(keybytes, (byte) 0x00);

			/* digest & obtain the final key */
			shaDigest.reset();
			final byte[] finalkey = shaDigest.digest(salted);
			java.util.Arrays.fill(salted, (byte) 0x00);

			/* init key specifications */
			final java.security.spec.KeySpec keyspec = new javax.crypto.spec.PBEKeySpec(
														new String(finalkey).toCharArray(), salt, 2048, 256);
			
			/* init key factory */
			final javax.crypto.SecretKeyFactory factory = javax.crypto.SecretKeyFactory.getInstance(
															ds.safelock.config.Configuration.KEYFACTORY_ALGORITHM);
			
			/* obtain the key */
			final javax.crypto.SecretKey secretkey = new javax.crypto.spec.SecretKeySpec(
															factory.generateSecret(keyspec).getEncoded(), 
															ds.safelock.config.Configuration.KEY_ALGORITHM);
			java.util.Arrays.fill(finalkey, (byte) 0x00);

			/* init the cipher */
			final javax.crypto.Cipher c = javax.crypto.Cipher.getInstance(ds.safelock.config.Configuration.CIPHER_ALGORITHM);
			c.init(javax.crypto.Cipher.DECRYPT_MODE, secretkey, new javax.crypto.spec.IvParameterSpec(iv));
			
			/* deciphers */
			return c.doFinal(data);
		}
		catch (java.security.InvalidKeyException e) { System.err.println(e.getMessage()); }
		//decipher related
		catch (java.security.InvalidAlgorithmParameterException e) { System.err.println(e.getMessage()); }
		catch (javax.crypto.IllegalBlockSizeException e) { System.err.println(e.getMessage()); }
		catch (javax.crypto.BadPaddingException e) { System.err.println(e.getMessage()); }
		catch (java.security.NoSuchAlgorithmException e) { System.err.println(e.getMessage()); } 
		catch (javax.crypto.NoSuchPaddingException e) { System.err.println(e.getMessage()); }
		catch (java.security.spec.InvalidKeySpecException e) { System.err.println(e.getMessage()); } 
		catch (java.io.UnsupportedEncodingException e) { System.err.println(e.getMessage()); }
		return null;
	}
}
